package cn.yangliu.jpa.handler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import cn.yangliu.jpa.annotations.JpaQuery;
import cn.yangliu.jpa.enums.LinkTypeEnum;
import cn.yangliu.jpa.enums.QueryTypeEnum;
import cn.yangliu.jpa.function.PredicateBuilder;
import cn.yangliu.jpa.tools.JpaUtils;
import lombok.SneakyThrows;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.jpa.domain.Specification;

/**
 * @author 杨柳
 * @date 2019-06-15
 */
class SpecificationHandler {

    private SpecificationHandler() {
    }

    /**
     * 构建Specification 查询条件
     *
     * @param queryPojo 查询封装
     * @param <T>       实体类型
     * @return Specification
     */
    @SneakyThrows
    static <T> Specification<T> buildSpecification(Object queryPojo) {
        return buildSpecification(queryPojo, null);
    }

    /**
     * 构建Specification 查询条件
     *
     * @param queryPojo 查询封装
     * @param builder   builder
     * @param <T>       实体类型
     * @return Specification
     */
    @SneakyThrows
    static <T> Specification<T> buildSpecification(Object queryPojo, PredicateBuilder builder) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (Objects.isNull(queryPojo)) {
                return cb.and(predicates.toArray(new Predicate[]{}));
            }

            buildPredicates(queryPojo, builder, root, cb, predicates);

            return cb.and(predicates.toArray(new Predicate[]{}));
        };
    }

    /**
     * @param queryPojo  查询对象
     * @param builder    PredicateBuilder
     * @param root       Root
     * @param cb         CriteriaBuilder
     * @param predicates predicates
     * @param <T>        泛型
     */
    private static <T> void buildPredicates(Object queryPojo, PredicateBuilder builder, Root<T> root,
                                            CriteriaBuilder cb,
                                            List<Predicate> predicates) {
        List<Field> fieldList = JpaUtils.getDeclaredFields(queryPojo);

        for (Field field : fieldList) {

            Object value = JpaUtils.getFieldValue(queryPojo, field);
            if (Objects.isNull(value) || Objects.equals(value, "")) {
                continue;
            }

            JpaQuery jpaQuery = field.getDeclaredAnnotation(JpaQuery.class);

            if (Objects.isNull(jpaQuery)) {
                continue;
            }

            LinkTypeEnum linkType = jpaQuery.link();

            String fieldName = jpaQuery.field();
            fieldName = StringUtils.isBlank(fieldName) ? field.getName() : fieldName;

            QueryTypeEnum queryTypeEnum = jpaQuery.type();

            Predicate predicate = null;

            if (Objects.nonNull(builder)) {
                predicate = builder.build(root, cb, fieldName, value);
            }

            if (Objects.isNull(predicate)) {
                predicate = buildPredicate(root, cb, value, fieldName, queryTypeEnum);
            }

            if (Objects.isNull(predicate)) {
                continue;
            }
            if (Objects.equals(linkType, LinkTypeEnum.OR)) {
                predicate = cb.or(predicate);
            } else {
                predicate = cb.and(predicate);
            }
            predicates.add(predicate);
        }
    }


    /**
     * 构建Predicate
     *
     * @param root          Root
     * @param cb            CriteriaBuilder
     * @param value         属性值
     * @param fieldName     属性名
     * @param queryTypeEnum 查询方式
     * @param <T>           泛型
     * @return Predicate
     */
    private static <T> Predicate buildPredicate(Root<T> root, CriteriaBuilder cb, Object value, String fieldName,
                                                QueryTypeEnum queryTypeEnum) {
        Predicate predicate = null;

        if (Objects.equals(QueryTypeEnum.LIKE, queryTypeEnum)) {
            Path<String> path = root.get(fieldName);
            predicate = cb.like(path, "%" + value.toString() + "%");
        } else if (Objects.equals(QueryTypeEnum.EQ, queryTypeEnum)) {
            Path<?> path = root.get(fieldName);
            predicate = cb.equal(path, value);
        } else if (Objects.equals(QueryTypeEnum.LT, queryTypeEnum)) {
            Path<Comparable> path = root.get(fieldName);
            predicate = cb.lessThan(path, (Comparable) value);
        } else if (Objects.equals(QueryTypeEnum.GT, queryTypeEnum)) {
            Path<Comparable> path = root.get(fieldName);
            predicate = cb.greaterThan(path, (Comparable) value);
        } else if (Objects.equals(QueryTypeEnum.LTE, queryTypeEnum)) {
            Path<Comparable> path = root.get(fieldName);
            predicate = cb.lessThanOrEqualTo(path, (Comparable) value);
        } else if (Objects.equals(QueryTypeEnum.GTE, queryTypeEnum)) {
            Path<Comparable> path = root.get(fieldName);
            predicate = cb.greaterThanOrEqualTo(path, (Comparable) value);
        } else if (Objects.equals(QueryTypeEnum.NOTEQ, queryTypeEnum)) {
            Path<?> path = root.get(fieldName);
            predicate = cb.notEqual(path, value);
        }
        return predicate;
    }
}
